Conversation
🦋 Changeset detectedLatest commit: 073ee72 The changes in this PR will be included in the next version bump. This PR includes changesets to release 4 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdded a Lepus (main-thread) hooks runtime, exposed stable runtime constants and new package export subpaths for hooks/constants, and updated rspeedy aliasing and tests to resolve hook imports differently per execution layer. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
packages/react/runtime/lepus/hooks/index.js (1)
60-68:useReducerdispatch is a no-op - state updates won't work.The dispatch function on line 64 is
function(action) {}, which silently discards all state updates. If this is intentional for a single-render main-thread context (matching the no-opuseEffect/useLayoutEffect), consider adding a brief comment to document this design choice for maintainability.📝 Suggested documentation
function useReducer(reducer, initialState, init) { var hookState = getHookState(currentIndex++, 2); hookState._reducer = reducer; if (!hookState[COMPONENT]) { + // Dispatch is no-op in main-thread runtime (single render, no state updates) hookState[VALUE] = [!init ? invokeOrReturn(undefined, initialState) : init(initialState), function(action) {}]; hookState[COMPONENT] = currentComponent; } return hookState[VALUE]; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/react/runtime/lepus/hooks/index.js` around lines 60 - 68, useReducer's dispatch is currently a no-op (function(action) {}), so state never updates; replace that no-op with a dispatch that uses hookState._reducer to compute the next state, assigns it into hookState[VALUE][0], and triggers the component update via the runtime's render scheduler (use currentComponent or whatever local scheduler API is available) so the component re-renders with the new state; if the no-op was intentional, instead add a one-line comment inside useReducer next to the dispatch (referencing useReducer, hookState, hookState._reducer, hookState[VALUE], currentComponent) explaining the single-render/main-thread design decision.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/react/runtime/__test__/debug/react-hooks-profile.test.jsx`:
- Line 12: The test environment switch to background mode allows
BackgroundSnapshotInstance state to bleed because
snapshotInstanceManager.clear() is no longer resetting the active tree; update
the environment reset so both backgroundSnapshotInstanceManager and
snapshotInstanceManager are cleared before reinitializing: modify resetEnv() (or
the same routine invoked when switching runtimes, referenced by
globalEnvManager/resetEnv) to call backgroundSnapshotInstanceManager.clear() in
addition to snapshotInstanceManager.clear() so background state is fully reset
between cases.
In `@packages/react/runtime/lepus/hooks/index.js`:
- Around line 118-123: The useId implementation always returns "P0-0" because
mask is a local array recreated on each call; fix by using persistent storage
for the counters (either a module-level counter or store the mask/counter on the
hook state returned by getHookState). Modify function useId so it
reads/increments a persistent counter (e.g., module-scope idCounter or
state.counter on the array returned by getHookState(currentIndex++, 11)) and
then builds state[VALUE] = 'P' + <bucket> + '-' + <incremented counter>; ensure
you reference currentIndex, getHookState, state and VALUE so the counter
survives across calls and produces unique IDs.
In `@packages/rspeedy/plugin-react-alias/test/index.test.ts`:
- Around line 341-357: The test's expectation for backgroundRule.resolve.alias
of 'preact/hooks' is incorrect because the plugin implementation
(plugin-react-alias's resolvePreact('preact/hooks') in
packages/rspeedy/plugin-react-alias/src/index.ts) currently maps 'preact/hooks'
to the preact-specific path, not '/packages/react/runtime/lib/hooks/react.js';
either update the test to expect the path produced by
resolvePreact('preact/hooks') (inspect resolvePreact or
backgroundRule.resolve.alias at runtime) or change the plugin implementation in
plugin-react-alias (the resolvePreact call or alias mapping logic in
src/index.ts) to point 'preact/hooks' to
'/packages/react/runtime/lib/hooks/react.js' so the assertion matches the actual
alias.
---
Nitpick comments:
In `@packages/react/runtime/lepus/hooks/index.js`:
- Around line 60-68: useReducer's dispatch is currently a no-op
(function(action) {}), so state never updates; replace that no-op with a
dispatch that uses hookState._reducer to compute the next state, assigns it into
hookState[VALUE][0], and triggers the component update via the runtime's render
scheduler (use currentComponent or whatever local scheduler API is available) so
the component re-renders with the new state; if the no-op was intentional,
instead add a one-line comment inside useReducer next to the dispatch
(referencing useReducer, hookState, hookState._reducer, hookState[VALUE],
currentComponent) explaining the single-render/main-thread design decision.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: de50334b-3441-43f3-a847-a673c63aae37
📒 Files selected for processing (12)
.changeset/tidy-buttons-tie.mdpackages/react/package.jsonpackages/react/runtime/__test__/debug/react-hooks-profile.test.jsxpackages/react/runtime/__test__/hooks/useLynxGlobalEventListener.test.jsxpackages/react/runtime/lazy/internal.jspackages/react/runtime/lepus/hooks/index.jspackages/react/runtime/lepus/jsx-runtime/index.jspackages/react/runtime/src/constants.tspackages/react/runtime/src/internal.tspackages/react/runtime/vitest.config.tspackages/rspeedy/plugin-react-alias/src/index.tspackages/rspeedy/plugin-react-alias/test/index.test.ts
💤 Files with no reviewable changes (2)
- packages/react/runtime/src/internal.ts
- packages/react/runtime/lazy/internal.js
fd3c796 to
4b259d2
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/rspeedy/plugin-react-alias/src/index.ts (1)
131-140:⚠️ Potential issue | 🟠 MajorAlias
@lynx-js/react/lepus/hooksin the background rule as well.The main-thread alias block (lines 120–128) includes
.set('@lynx-js/react/lepus/hooks', reactHooks.mainThread), but the background rule (lines 131–140) omits it. Background code that explicitly imports@lynx-js/react/lepus/hookswill resolve to the main-thread implementation instead of the background version, causing a layer mismatch.Proposed fix
.alias .set('react/jsx-runtime', jsxRuntime.background) .set('react/jsx-dev-runtime', jsxDevRuntime.background) .set('@lynx-js/react/jsx-runtime', jsxRuntime.background) .set('@lynx-js/react/jsx-dev-runtime', jsxDevRuntime.background) .set('preact/hooks', reactHooks.preact) .set('@lynx-js/react/hooks', reactHooks.background) + .set('@lynx-js/react/lepus/hooks', reactHooks.background) .end()🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/rspeedy/plugin-react-alias/src/index.ts` around lines 131 - 140, The background alias rule (.rule('react:jsx-runtime:background') with issuerLayer(LAYERS.BACKGROUND)) is missing an alias for '@lynx-js/react/lepus/hooks', causing imports to resolve to the main-thread version; add .set('@lynx-js/react/lepus/hooks', reactHooks.background) to the chain alongside the existing reactHooks mappings so background imports resolve to the background implementation.
♻️ Duplicate comments (1)
packages/react/runtime/lepus/hooks/index.js (1)
118-122:⚠️ Potential issue | 🔴 Critical
useIdstill returns the same ID for every call.
maskis recreated on each invocation, somask[1]++always starts from0and every component getsP0-0.Proposed fix
+var idMask = [0, 0]; + function useId() { var state = getHookState(currentIndex++, 11); - var mask = [0, 0]; - state[VALUE] = 'P' + mask[0] + '-' + mask[1]++; + if (state[VALUE] == null) { + state[VALUE] = 'P' + idMask[0] + '-' + idMask[1]++; + } return state[VALUE]; }In React and Preact, should `useId` return a stable unique ID per hook call rather than the same ID for every component?🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/react/runtime/lepus/hooks/index.js` around lines 118 - 122, useId currently recreates mask on every call so every hook returns "P0-0"; fix by making the ID persistent in the hook state instead of recomputing: inside useId (which calls getHookState and uses currentIndex and VALUE) only compute and assign state[VALUE] when it's not already set — e.g. initialize a stable per-hook counter or store the mask/counter in the returned state array so subsequent calls reuse it (use getHookState's state slot rather than local var mask) and increment a module-level counter or the stored counter when generating new IDs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/react/runtime/lepus/hooks/index.js`:
- Around line 84-87: The current useMemo implementation always calls factory()
and ignores the dependency array (args), causing values to change every render;
modify useMemo to store previous deps in the hook state (e.g., state[ARGS]) and
only call factory() when deps require recomputation: if args is undefined behave
like always-recompute; if args is an array and there are previous deps do a
shallow equality check (compare length and each item via ===) and only run
factory() and update state[VALUE] and state[ARGS] when any item changed;
otherwise return the existing state[VALUE]; implement the shallow compare inline
or as a small helper and keep references to currentIndex, getHookState, VALUE,
and ARGS symbols when updating state.
- Around line 60-67: The dispatched function created in useReducer is a no-op;
replace it with a real dispatcher that captures the hookState and reducer, calls
reducer(currentState, action), compares with Object.is, and if different stores
the new state into hookState[VALUE][0] and triggers a re-render by calling
hookState[COMPONENT].setState({}); ensure you create the dispatcher inside
useReducer so it closes over hookState and reducer (referencing symbols:
useReducer, hookState, VALUE, COMPONENT, reducer, Object.is, invokeOrReturn,
currentComponent, getHookState) and updates state only when Object.is reports a
change.
---
Outside diff comments:
In `@packages/rspeedy/plugin-react-alias/src/index.ts`:
- Around line 131-140: The background alias rule
(.rule('react:jsx-runtime:background') with issuerLayer(LAYERS.BACKGROUND)) is
missing an alias for '@lynx-js/react/lepus/hooks', causing imports to resolve to
the main-thread version; add .set('@lynx-js/react/lepus/hooks',
reactHooks.background) to the chain alongside the existing reactHooks mappings
so background imports resolve to the background implementation.
---
Duplicate comments:
In `@packages/react/runtime/lepus/hooks/index.js`:
- Around line 118-122: useId currently recreates mask on every call so every
hook returns "P0-0"; fix by making the ID persistent in the hook state instead
of recomputing: inside useId (which calls getHookState and uses currentIndex and
VALUE) only compute and assign state[VALUE] when it's not already set — e.g.
initialize a stable per-hook counter or store the mask/counter in the returned
state array so subsequent calls reuse it (use getHookState's state slot rather
than local var mask) and increment a module-level counter or the stored counter
when generating new IDs.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: eb5652ea-331f-444b-8f69-3d715bd8f658
📒 Files selected for processing (12)
.changeset/tidy-buttons-tie.mdpackages/react/package.jsonpackages/react/runtime/__test__/debug/react-hooks-profile.test.jsxpackages/react/runtime/__test__/hooks/useLynxGlobalEventListener.test.jsxpackages/react/runtime/lazy/internal.jspackages/react/runtime/lepus/hooks/index.jspackages/react/runtime/lepus/jsx-runtime/index.jspackages/react/runtime/src/constants.tspackages/react/runtime/src/internal.tspackages/react/runtime/vitest.config.tspackages/rspeedy/plugin-react-alias/src/index.tspackages/rspeedy/plugin-react-alias/test/index.test.ts
💤 Files with no reviewable changes (2)
- packages/react/runtime/src/internal.ts
- packages/react/runtime/lazy/internal.js
✅ Files skipped from review due to trivial changes (5)
- packages/react/runtime/test/hooks/useLynxGlobalEventListener.test.jsx
- .changeset/tidy-buttons-tie.md
- packages/react/runtime/lepus/jsx-runtime/index.js
- packages/react/runtime/src/constants.ts
- packages/react/runtime/vitest.config.ts
🚧 Files skipped from review as they are similar to previous changes (3)
- packages/react/runtime/test/debug/react-hooks-profile.test.jsx
- packages/rspeedy/plugin-react-alias/test/index.test.ts
- packages/react/package.json
Merging this PR will degrade performance by 15.45%
Performance Changes
Comparing Footnotes
|
React External#424 Bundle Size — 580.35KiB (-0.42%).073ee72(current) vs d1c16c5 main#418(baseline) Bundle metrics
Bundle size by type
Bundle analysis report Branch p/hooks Project dashboard Generated by RelativeCI Documentation Report issue |
Web Explorer#8881 Bundle Size — 749.3KiB (0%).073ee72(current) vs d1c16c5 main#8874(baseline) Bundle metrics
Bundle size by type
|
| Current #8881 |
Baseline #8874 |
|
|---|---|---|
401.63KiB |
401.63KiB |
|
345.51KiB |
345.51KiB |
|
2.16KiB |
2.16KiB |
Bundle analysis report Branch p/hooks Project dashboard
Generated by RelativeCI Documentation Report issue
React MTF Example#440 Bundle Size — 191.12KiB (-0.9%).073ee72(current) vs d1c16c5 main#433(baseline) Bundle metrics
Bundle size by type
Bundle analysis report Branch p/hooks Project dashboard Generated by RelativeCI Documentation Report issue |
React Example#7307 Bundle Size — 220.51KiB (-1.37%).073ee72(current) vs d1c16c5 main#7300(baseline) Bundle metrics
Bundle size by type
Bundle analysis report Branch p/hooks Project dashboard Generated by RelativeCI Documentation Report issue |
There was a problem hiding this comment.
♻️ Duplicate comments (3)
packages/react/runtime/lepus/hooks/index.js (3)
60-67:⚠️ Potential issue | 🔴 Critical
useReducerdispatch is still a no-op, so state updates are dropped.On Line 64, the dispatcher never calls
reducer, never writes next state, and never schedules a render. This breaks bothuseReduceranduseStateupdate flow.🐛 Proposed fix
function useReducer(reducer, initialState, init) { var hookState = getHookState(currentIndex++, 2); hookState._reducer = reducer; if (!hookState[COMPONENT]) { - hookState[VALUE] = [!init ? invokeOrReturn(undefined, initialState) : init(initialState), function(action) {}]; + hookState[VALUE] = [!init ? invokeOrReturn(undefined, initialState) : init(initialState), function(action) { + var currentValue = hookState[VALUE][0]; + var nextValue = hookState._reducer(currentValue, action); + if (!Object.is(currentValue, nextValue)) { + hookState[VALUE][0] = nextValue; + hookState[COMPONENT].setState({}); + } + }]; hookState[COMPONENT] = currentComponent; } return hookState[VALUE]; }In Preact hooks, what does the `dispatch` function returned by `useReducer` do when next state differs from current state?🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/react/runtime/lepus/hooks/index.js` around lines 60 - 67, The dispatch returned from useReducer is a no-op; change the inline dispatch (the second element of hookState[VALUE]) to accept an action, obtain the current state from hookState[VALUE][0], compute nextState by calling hookState._reducer(currentState, action), compare nextState to currentState (strict equality), and if different assign hookState[VALUE][0] = nextState and schedule a component update (call the framework's render/enqueue update helper for currentComponent) so the component re-renders; ensure you reference hookState._reducer, hookState[VALUE], and currentComponent when implementing this.
84-87:⚠️ Potential issue | 🔴 Critical
useMemoignores dependencies and recomputes every render.Lines 84-87 always call
factory(). This breaks memoization and invalidatesuseCallback/useRefstability guarantees.🐛 Proposed fix
function useMemo(factory, args) { var state = getHookState(currentIndex++, 7); - state[VALUE] = factory(); + if (!args || !state._args || argsChanged(state._args, args)) { + state[VALUE] = factory(); + state._args = args; + } return state[VALUE]; } + +function argsChanged(oldArgs, newArgs) { + if (oldArgs.length !== newArgs.length) return true; + for (var i = 0; i < oldArgs.length; i++) { + if (!Object.is(oldArgs[i], newArgs[i])) return true; + } + return false; +}In current Preact hooks, does `useMemo` reuse cached value when dependency values are unchanged, and are `useCallback`/`useRef` implemented on top of that behavior?🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/react/runtime/lepus/hooks/index.js` around lines 84 - 87, useMemo currently always calls factory() and ignores dependencies; change useMemo (the function using getHookState and currentIndex) to read the previous deps from the hook state, perform a shallow equality check between prevDeps and the incoming args, and only call factory() and update state[VALUE] when args are provided and differ (if args is undefined, preserve existing React/Preact semantics and recompute every render). Store the new deps into the hook state (e.g., state[DEPS] or state[1]) after computing so subsequent renders can compare; this will restore memoization used by useCallback/useRef which depend on stable values from useMemo. Ensure the comparison checks length and each element with === (shallow).
118-123:⚠️ Potential issue | 🔴 Critical
useIdcurrently generates the same ID (P0-0) for all calls.Line 120 recreates
maskon each invocation, so Line 121 never advances globally and IDs are not unique.🐛 Proposed fix
+var idMask = [0, 0]; + function useId() { var state = getHookState(currentIndex++, 11); - var mask = [0, 0]; - state[VALUE] = 'P' + mask[0] + '-' + mask[1]++; + if (!state[VALUE]) { + state[VALUE] = 'P' + idMask[0] + '-' + idMask[1]++; + } return state[VALUE]; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/react/runtime/lepus/hooks/index.js` around lines 118 - 123, useId currently creates a new local mask on each call so IDs never advance; persist the counters instead of recreating mask each invocation by storing the counter(s) in the hook state returned by getHookState (or in a module-level counter) and update them when generating the ID. Specifically, in useId, read or initialize a persistent counter from state (e.g., state.counter or state[VALUE] slot), compute the ID using that counter (replacing the local mask), increment/rollover the counters appropriately, assign the generated ID back to state[VALUE], and return it so subsequent calls produce unique IDs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@packages/react/runtime/lepus/hooks/index.js`:
- Around line 60-67: The dispatch returned from useReducer is a no-op; change
the inline dispatch (the second element of hookState[VALUE]) to accept an
action, obtain the current state from hookState[VALUE][0], compute nextState by
calling hookState._reducer(currentState, action), compare nextState to
currentState (strict equality), and if different assign hookState[VALUE][0] =
nextState and schedule a component update (call the framework's render/enqueue
update helper for currentComponent) so the component re-renders; ensure you
reference hookState._reducer, hookState[VALUE], and currentComponent when
implementing this.
- Around line 84-87: useMemo currently always calls factory() and ignores
dependencies; change useMemo (the function using getHookState and currentIndex)
to read the previous deps from the hook state, perform a shallow equality check
between prevDeps and the incoming args, and only call factory() and update
state[VALUE] when args are provided and differ (if args is undefined, preserve
existing React/Preact semantics and recompute every render). Store the new deps
into the hook state (e.g., state[DEPS] or state[1]) after computing so
subsequent renders can compare; this will restore memoization used by
useCallback/useRef which depend on stable values from useMemo. Ensure the
comparison checks length and each element with === (shallow).
- Around line 118-123: useId currently creates a new local mask on each call so
IDs never advance; persist the counters instead of recreating mask each
invocation by storing the counter(s) in the hook state returned by getHookState
(or in a module-level counter) and update them when generating the ID.
Specifically, in useId, read or initialize a persistent counter from state
(e.g., state.counter or state[VALUE] slot), compute the ID using that counter
(replacing the local mask), increment/rollover the counters appropriately,
assign the generated ID back to state[VALUE], and return it so subsequent calls
produce unique IDs.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 325964a9-4c9f-41c6-b01f-fc1d2ca2aeae
📒 Files selected for processing (12)
.changeset/tidy-buttons-tie.mdpackages/react/package.jsonpackages/react/runtime/__test__/debug/react-hooks-profile.test.jsxpackages/react/runtime/__test__/hooks/useLynxGlobalEventListener.test.jsxpackages/react/runtime/lazy/internal.jspackages/react/runtime/lepus/hooks/index.jspackages/react/runtime/lepus/jsx-runtime/index.jspackages/react/runtime/src/constants.tspackages/react/runtime/src/internal.tspackages/react/runtime/vitest.config.tspackages/rspeedy/plugin-react-alias/src/index.tspackages/rspeedy/plugin-react-alias/test/index.test.ts
💤 Files with no reviewable changes (2)
- packages/react/runtime/src/internal.ts
- packages/react/runtime/lazy/internal.js
✅ Files skipped from review due to trivial changes (5)
- packages/react/runtime/test/debug/react-hooks-profile.test.jsx
- .changeset/tidy-buttons-tie.md
- packages/react/runtime/vitest.config.ts
- packages/react/runtime/test/hooks/useLynxGlobalEventListener.test.jsx
- packages/react/runtime/lepus/jsx-runtime/index.js
🚧 Files skipped from review as they are similar to previous changes (3)
- packages/react/runtime/src/constants.ts
- packages/react/package.json
- packages/rspeedy/plugin-react-alias/src/index.ts
d04260b to
e18cc33
Compare
Yradex
left a comment
There was a problem hiding this comment.
useId() currently recreates mask = [0, 0] on every call, so it always returns P0-0.
That means multiple useId() calls in the same tree will collide, which breaks any logic relying on stable unique IDs. The new test only covers a single call, so this can still pass with green CI.
Could we make the generated id persistent across hook calls instead of rebuilding the counter each time?
fb3eebf to
b0c07e5
Compare
Yradex
left a comment
There was a problem hiding this comment.
The follow-up commits addressed the useId issue I called out earlier, and I don't see any remaining blocking problems in the latest revision.
This PR was opened by the [Changesets release](https://github.com/changesets/action) GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated. # Releases ## @lynx-js/react@0.119.0 ### Minor Changes - Simplify hooks for main-thread runtime, which only can run during the first screen. ([#2441](#2441)) ### Patch Changes - Remove stale gestures when gestures are removed ([#2297](#2297)) - Trace refactor ([#2466](#2466)) - Remove `ReactLynx::renderOpcodes` from the trace - Use `ReactLynx::transferRoot` to measure the time spent transferring the root to the background thread - refactor: set state of suspense to render fallback ([#2450](#2450)) - Support rstest for testing library, you can use rstest with RLTL now: ([#2328](#2328)) Create a config file `rstest.config.ts` with the following content: ```ts import { defineConfig } from "@rstest/core"; import { withLynxConfig } from "@lynx-js/react/testing-library/rstest-config"; export default defineConfig({ extends: withLynxConfig(), }); ``` `@lynx-js/react/testing-library/rstest-config` will automatically load your `lynx.config.ts` and apply the same configuration to rstest, so you can keep your test environment consistent with your development environment. And then use rstest as usual: ```bash $ rstest ``` For more usage detail, see <https://rstest.rs/> - Update preact version ([#2456](#2456)) - Add `nodeIndex` to generated FiberElement creation calls and expose React transform debug metadata as `uiSourceMapRecords`. ([#2402](#2402)) ## @lynx-js/react-rsbuild-plugin@0.16.0 ### Minor Changes - Simplify hooks for main-thread runtime, which only can run during the first screen. ([#2441](#2441)) ### Patch Changes - Support rstest for testing library using a dedicated testing loader. ([#2328](#2328)) - Fix `environments.lynx.performance.profile` so it overrides the default profile behavior for the current environment. ([#2468](#2468)) - Add `enableUiSourceMap` option to enable UI source map generation and debug-metadata asset emission. ([#2402](#2402)) - Updated dependencies \[[`a9f8d05`](a9f8d05), [`b1ad1b9`](b1ad1b9), [`f6184f3`](f6184f3), [`f6184f3`](f6184f3), [`a9f8d05`](a9f8d05), [`f542d9c`](f542d9c)]: - @lynx-js/template-webpack-plugin@0.10.9 - @lynx-js/react-webpack-plugin@0.9.1 - @lynx-js/react-alias-rsbuild-plugin@0.16.0 - @lynx-js/css-extract-webpack-plugin@0.7.0 - @lynx-js/react-refresh-webpack-plugin@0.3.5 - @lynx-js/use-sync-external-store@1.5.0 ## @lynx-js/react-alias-rsbuild-plugin@0.16.0 ### Minor Changes - Simplify hooks for main-thread runtime, which only can run during the first screen. ([#2441](#2441)) ### Patch Changes - fix(rstest): add global fallback aliases for `@lynx-js/react/jsx-runtime` and `@lynx-js/react/jsx-dev-runtime` ([#2464](#2464)) `pluginReactAlias` only aliased these entries inside layer-specific rules (`issuerLayer: BACKGROUND/MAIN_THREAD`). In rstest mode there are no layers, so JSX transformed by the testing loader—which emits `import { jsx } from '@lynx-js/react/jsx-runtime'`—could not be resolved, causing a `Cannot find module '@lynx-js/react/jsx-runtime'` error. Added global (non-layer-specific) fallback aliases pointing to the background jsx-runtime. ## @lynx-js/testing-environment@0.2.0 ### Minor Changes - **BREAKING CHANGE**: ([#2328](#2328)) Align the public test-environment API around `LynxEnv`. `LynxTestingEnv` now expects a `{ window }`-shaped environment instead of relying on a concrete `JSDOM` instance or `global.jsdom`. Callers that construct `LynxTestingEnv` manually or initialize the environment through globals should migrate to `new LynxTestingEnv({ window })` or set `global.lynxEnv`. This release also adds the `@lynx-js/testing-environment/env/rstest` entry for running the shared testing-environment suite under rstest. ### Patch Changes - Add `__RemoveGestureDetector` PAPI binding ([#2297](#2297)) ## @lynx-js/rspeedy@0.14.2 ### Patch Changes - Updated dependencies \[]: - @lynx-js/web-rsbuild-server-middleware@0.20.2 ## create-rspeedy@0.14.2 ### Patch Changes - Add Rstest ReactLynx Testing Library template. ([#2328](#2328)) ## @lynx-js/external-bundle-rsbuild-plugin@0.1.1 ### Patch Changes - Updated dependencies \[[`3262ca8`](3262ca8)]: - @lynx-js/externals-loading-webpack-plugin@0.1.1 ## @lynx-js/web-core@0.20.2 ### Patch Changes - fix: map clientX and clientY to x and y in touch event detail ([#2458](#2458)) - fix(web-platform): completely detach event listeners and forcefully free `MainThreadWasmContext` pointer alongside strict FIFO async component disposal to ensure total memory reclamation without use-after-free risks ([#2457](#2457)) - refactor: with WeakRef in element APIs and WASM bindings to improve memory management. ([#2439](#2439)) - fix: preserve CSS variable fallback values when encoding web-core stylesheets so declarations like `var(--token, rgba(...))` are emitted with their fallback intact. ([#2460](#2460)) - fix: avoid to do use-after-free for rust instance ([#2461](#2461)) - fix: Change uniqueId to uid in LynxCrossThreadEventTarget ([#2467](#2467)) - Updated dependencies \[]: - @lynx-js/web-worker-rpc@0.20.2 ## @lynx-js/externals-loading-webpack-plugin@0.1.1 ### Patch Changes - fix: deduplicate `loadScript` calls for externals sharing the same (bundle, section) pair ([#2465](#2465)) When multiple externals had different `libraryName` values but pointed to the same bundle URL and section path, `createLoadExternalSync`/`createLoadExternalAsync` was called once per external, causing `lynx.loadScript` to execute redundantly for the same section. Now only the first external in each `(url, sectionPath)` group triggers the load; subsequent externals in the group are assigned the already-loaded result directly. ## @lynx-js/react-webpack-plugin@0.9.1 ### Patch Changes - Support rstest for testing library using a dedicated testing loader. ([#2328](#2328)) - fix(rstest): normalize partial `compat` options in the testing loader ([#2464](#2464)) The testing loader forwards `compat` directly to `transformReactLynxSync` without normalization. When `compat` is supplied as a partial object, the required `target` field is absent and the WASM transform throws `Error: Missing field 'target'`. Added the same normalization already present in `getCommonOptions` for the background/main-thread loaders: fills in `target: 'MIXED'` and all other required fields with sensible defaults. - Add `enableUiSourceMap` option to enable UI source map generation and debug-metadata asset emission. ([#2402](#2402)) ## @lynx-js/template-webpack-plugin@0.10.9 ### Patch Changes - Introduce `LynxDebugMetadataPlugin` to emit debug-metadata assets. ([#2402](#2402)) - Updated dependencies \[[`24c4c69`](24c4c69), [`7332eb4`](7332eb4), [`fd0cc6e`](fd0cc6e), [`e5b0f66`](e5b0f66), [`5aa97d8`](5aa97d8), [`5c39654`](5c39654)]: - @lynx-js/web-core@0.20.2 ## @lynx-js/react-umd@0.119.0 ## upgrade-rspeedy@0.14.2 ## @lynx-js/web-rsbuild-server-middleware@0.20.2 ## @lynx-js/web-worker-rpc@0.20.2 Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Summary by CodeRabbit
New Features
Tests
Refactor
Chores
Checklist